/*
  author Sylvain Bertrand <digital.ragnarok@gmail.com>
  Protected by GNU Affero GPL v3 with some exceptions.
  See README at root of alga tree.
*/
#include <linux/pci.h>
#include <linux/cdev.h>

#include <alga/alga.h>
#include <alga/pixel_fmts.h>
#include <alga/timing.h>
#include <alga/amd/atombios/atb.h>
#include <alga/amd/dce4/dce4.h>
#include <alga/amd/dce4/dce4_dev.h>

#include "types.h"
#include "ba.h"
#include "ucode.h"
#include "cp.h"
#include "gpu.h"
#include "irq.h"
#include "drv.h"

#include "regs.h"

void dce_hpd_polarity_rearm(struct device *dev, u8 hpd, bool connected)
{
	struct pci_dev *pdev;
	u32 tmp;

	pdev = container_of(dev, struct pci_dev, dev);
	tmp = rr32(pdev, regs_hpd_int_ctl[hpd]);
	if (connected)
		tmp &= ~DC_HPDx_INT_CTL_INT_POLARITY;
	else
		tmp |= DC_HPDx_INT_CTL_INT_POLARITY;
	wr32(pdev, tmp, regs_hpd_int_ctl[hpd]);
}

bool dce_hpd_sense(struct device *dev, u8 hpd)
{
	struct pci_dev *pdev;
	pdev = container_of(dev, struct pci_dev, dev);
	return ((rr32(pdev, regs_hpd_int_status[hpd])
					& DC_HPDx_INT_STATUS_SENSE) != 0);
}

void dce_hpd_on(struct device *dev, u8 hpd)
{
	struct pci_dev *pdev;
	u32 tmp;

	pdev = container_of(dev, struct pci_dev, dev);

	/*
 	 * This is hardcoded for displayport with the 2 ms threshold in mind.
 	 * In theory, the irq is not supposed to be raised until the hpd logic
 	 * knows if it is a disconnection or a dp sink irq.
	 */
	tmp = DC_HPDx_CTL_CONN_TIMER(2500) | DC_HPDx_CTL_RX_INT_TIMER(250)
							| DC_HPDx_CTL_EN; 

	wr32(pdev, tmp, regs_hpd_ctl[hpd]);
}

void dce_hpd_off(struct device *dev, u8 hpd)
{
	struct pci_dev *pdev;

	pdev = container_of(dev, struct pci_dev, dev);
	wr32(pdev, 0, regs_hpd_ctl[hpd]);
}

int dce_crtc_fb(struct device *dev, unsigned crtc, u64 fb,
	enum alga_pixel_fmt pixel_fmt, unsigned w, unsigned h, unsigned pitch)
{
	struct pci_dev *pdev;
	u32 fb_fmt;
	u32 fb_swap;
	u32 viewport_w;
	u32 viewport_h;
	u32 crtc_offset;
	u32 tmp;

	pdev = container_of(dev, struct pci_dev, dev);

	crtc_offset = vals_crtc_offset[crtc];

	fb_swap = GRPH_ENDIAN_SWAP(GRPH_ENDIAN_NONE);
	switch (pixel_fmt) {
	case ALGA_ARGB6666:
	case ALGA_ARGB8888:
		fb_fmt = (GRPH_DEPTH(GRPH_DEPTH_32BPP)
						| GRPH_FMT(GRPH_FMT_ARGB8888));
#ifdef __BIG_ENDIAN
		fb_swap = GRPH_ENDIAN_SWAP(GRPH_ENDIAN_8IN32);
#endif
		break;
	case ALGA_ARGB2101010:
		fb_fmt = (GRPH_DEPTH(GRPH_DEPTH_32BPP)
						| GRPH_FMT(GRPH_FMT_ARGB8888));
#ifdef __BIG_ENDIAN
		fb_swap = GRPH_ENDIAN_SWAP(GRPH_ENDIAN_8IN32);
#endif
		break;
	default:
		dev_err(dev, "unsupported pixel format %d\n", pixel_fmt);
		return -EINVAL;
	}

	wr32(pdev, 0, regs_vga_ctl[crtc]);
	wr32(pdev, upper_32_bits(fb), GRPH_PRIMARY_SURFACE_ADDR_HIGH
								+ crtc_offset);
	wr32(pdev, upper_32_bits(fb), GRPH_SECONDARY_SURFACE_ADDR_HIGH
								+ crtc_offset);
	wr32(pdev, (u32)fb & GRPH_SURFACE_ADDR_MASK, GRPH_PRIMARY_SURFACE_ADDR
								+ crtc_offset);
	wr32(pdev, (u32)fb & GRPH_SURFACE_ADDR_MASK, GRPH_SECONDARY_SURFACE_ADDR
								+ crtc_offset);
	wr32(pdev, fb_fmt, GRPH_CTL + crtc_offset);
	wr32(pdev, fb_swap, GRPH_SWAP_CTL + crtc_offset);
	wr32(pdev, 0, GRPH_SURFACE_OFFSET_X + crtc_offset);
	wr32(pdev, 0, GRPH_SURFACE_OFFSET_Y + crtc_offset);
	/* pixels */
	wr32(pdev, 0, GRPH_X_START + crtc_offset);
	wr32(pdev, 0, GRPH_Y_START + crtc_offset);
	wr32(pdev, w, GRPH_X_END + crtc_offset);
	wr32(pdev, h, GRPH_Y_END + crtc_offset);
	wr32(pdev, pitch, GRPH_PITCH + crtc_offset);
	wr32(pdev, 1, GRPH_ENABLE + crtc_offset);
	wr32(pdev, h, DESKTOP_HEIGHT + crtc_offset);
	wr32(pdev, (0 << 16) | 0, VIEWPORT_START + crtc_offset);
	viewport_w = w;
	viewport_h = (h + 1) & ~1;
	wr32(pdev, (viewport_w << 16) | viewport_h, VIEWPORT_SZ + crtc_offset);

	/* pageflip setup */
	/* make sure flip is at vb rather than hb */
	tmp = rr32(pdev, GRPH_FLIP_CTL + crtc_offset);
	tmp &= ~GRPH_SURFACE_UPDATE_H_RETRACE_EN;
	wr32(pdev, tmp, GRPH_FLIP_CTL + crtc_offset);

	/* set pageflip to happen anywhere in vblank interval */
	wr32(pdev, 0, MASTER_UPDATE_MODE + crtc_offset);
	return 0;
}

void dce_lut(struct device *dev, unsigned crtc)
{
	struct pci_dev *pdev;
	u32 crtc_offset;
	unsigned i;
	u16 v;

	pdev = container_of(dev, struct pci_dev, dev);
	crtc_offset = vals_crtc_offset[crtc];

	wr32(pdev, 0, LUT_CTL + crtc_offset);

	wr32(pdev, 0, LUT_BLACK_OFFSET_BLUE + crtc_offset);
	wr32(pdev, 0, LUT_BLACK_OFFSET_GREEN + crtc_offset);
	wr32(pdev, 0, LUT_BLACK_OFFSET_RED + crtc_offset);

	wr32(pdev, 0xffff, LUT_WHITE_OFFSET_BLUE + crtc_offset);
	wr32(pdev, 0xffff, LUT_WHITE_OFFSET_GREEN + crtc_offset);
	wr32(pdev, 0xffff, LUT_WHITE_OFFSET_RED + crtc_offset);

	wr32(pdev, 0, LUT_RW_MODE + crtc_offset);
	wr32(pdev, 0x00000007, LUT_WRITE_EN_MASK + crtc_offset);

	wr32(pdev, 0, LUT_RW_IDX + crtc_offset);

	/*
	 * XXX: gamma hardcoded, 256 linear values of 10 bits, range 0x400
	 * (1024)
	 */
	for (v = 0, i = 0; i < 256; ++i, v+=4) {
		wr32(pdev, (v << 20) | (v << 10) | (v << 0), LUT_30_COLOR
								+ crtc_offset);
	}
}

void dce_irqs_ack(struct pci_dev *dev)
{
	struct dev_drv_data *drv_data;
	u32 disps[CRTCS_N_MAX];
	u32 grphs[CRTCS_N_MAX];
	unsigned i;
	u32 tmp;

	drv_data = pci_get_drvdata(dev);

	for (i = 0; i < drv_data->crtcs_n; ++i)
		disps[i] = rr32(dev, regs_disp_int_status[i]);

	for (i = 0; i < drv_data->crtcs_n; ++i)
		grphs[i] = rr32(dev, regs_crtc_grph_int_status[i]);

	for (i = 0; i < drv_data->crtcs_n; ++i)
		if (grphs[i] & GRPH_PFLIP_INT_OCCURRED)
			wr32(dev, GRPH_PFLIP_INT_CLEAR,
						regs_crtc_grph_int_status[i]);
	for (i = 0; i < drv_data->crtcs_n; ++i) {
		if (disps[i] & vals_lb_d_vblank_int[i])
			wr32(dev, VBLANK_ACK, regs_crtc_vblank_status[i]);
		if (disps[i] & vals_lb_d_vline_int[i])
			wr32(dev, VLINE_ACK, regs_crtc_vline_status[i]);
	}

	/* TODO: wrong, should use ih ring buffer, but it's ok for DCE intrs */
	drv_data->irq.hpd = 0;
	for (i = 0; i < drv_data->crtcs_n; ++i)
		if (disps[i] & vals_hpd_int[i]) {
			tmp = rr32(dev, regs_hpd_int_ctl[i]);
			tmp |= DC_HPDx_INT_CTL_INT_ACK;
			wr32(dev, tmp, regs_hpd_int_ctl[i]);
			drv_data->irq.hpd |= BIT(i);
		}
}

void dce_intrs_reset(struct pci_dev *dev)
{
	struct dev_drv_data *drv_data;
	unsigned i;
	u32 tmp;

	drv_data = pci_get_drvdata(dev);

	for (i = 0; i < drv_data->crtcs_n; ++i)
		wr32(dev, 0, regs_crtc_int_mask[i]);

	for (i = 0; i < drv_data->crtcs_n; ++i)
		wr32(dev, 0, regs_crtc_grph_int_ctl[i]);

	/* XXX: should know if we have a DAC before */
	wr32(dev, 0, DACA_AUTODETECT_INT_CTL);
	wr32(dev, 0, DACB_AUTODETECT_INT_CTL);

	/* keep polarity */
	for (i = 0; i < drv_data->crtcs_n; ++i) {
		tmp = rr32(dev, regs_hpd_int_ctl[i])
						& DC_HPDx_INT_CTL_INT_POLARITY;
		wr32(dev, tmp, regs_hpd_int_ctl[i]);
	}
}

int dce_intrs_enable(struct pci_dev *dev)
{
	struct dev_drv_data *drv_data;
	int r;
	u32 hpds[CRTCS_N_MAX];
	u32 crtcs[CRTCS_N_MAX];
	unsigned i;
	unsigned hpds_used;

	drv_data = pci_get_drvdata(dev);

	r = dce4_hpds_used(drv_data->dce, &hpds_used);
	if (r != 0)
		return r;

	for (i = 0; i < drv_data->crtcs_n; ++i) {
		if (hpds_used & BIT(i))
			hpds[i] = rr32(dev, regs_hpd_int_ctl[i])
						| DC_HPDx_INT_CTL_INT_EN;
		else
			hpds[i] = 0;

	}

	for (i = 0; i < drv_data->crtcs_n; ++i)
		crtcs[i] = VBLANK_INT_MASK;

	for (i = 0; i < drv_data->crtcs_n; ++i)
		wr32(dev, crtcs[i], regs_crtc_int_mask[i]);
	
	for (i = 0; i < drv_data->crtcs_n; ++i) 	
		wr32(dev, 0, regs_crtc_grph_int_ctl[i]);

	for (i = 0; i < drv_data->crtcs_n; ++i)
		wr32(dev, hpds[i], regs_hpd_int_ctl[i]);
	return 0;
}
